/* 
 * File:   Conexion.cpp
 * Author: Maximiliano Monzon (pd 82341)
 * 
 * Created on October 20, 2012, 9:03 AM
 */
#include <iostream>
#include <stdlib.h>
#include <errno.h>
#include "client_Conexion.h"
#include "common_ProtocoloConstantes.h"

using namespace TP::Comunicacion;

#define BUF_SIZE 500

Conexion::Conexion(const string& pIp, const string& pPuerto)
{
    this->ip = pIp;
    this->puerto = pPuerto;
    this->timeout = 0;
}

Conexion::Conexion(const string& pIp, const string& pPuerto, const int &pTimeout)
{
    this->ip = pIp;
    this->puerto = pPuerto;
    
    if (pTimeout > 0)
        this->timeout = pTimeout;
    else
        this->timeout = 0;
}

Conexion::~Conexion() {}

void Conexion::Iniciar() throw (Excepcion&){
    
    try
    {
        if ( ERROR_CONEXION == (this->sockfd = socket(AF_INET, SOCK_STREAM, 0) ))
        {
            string msg = "No se puede iniciar el socket. ";
            msg += strerror(errno);
            throw Excepcion ( msg);
        }
        
        struct sockaddr_in dest_addr;
        
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(atoi(this->puerto.c_str()));
        dest_addr.sin_addr.s_addr = inet_addr(this->ip.c_str());
        memset(&(dest_addr.sin_zero), '\0', 8);

        if (connect (this->sockfd, (struct sockaddr*) &dest_addr, sizeof (struct sockaddr)) == -1)
        {
            throw Excepcion ("No se puede conectar al host " 
                             + this->ip 
                             + " puerto " 
                             + this->puerto 
                             + ". "
                             + strerror (errno) );
        }
    }
    catch (Excepcion& pEx)
    {
        if (ERROR_CONEXION != this->sockfd)
            shutdown (this->sockfd, SHUT_RDWR);
        
        pEx.expandirMensaje("No se puede iniciar la conexion. ");
        throw;
    }
}

void Conexion::Finalizar() throw (Excepcion&){
    
    try
    {
        // Se finaliza la conexion.
        close(this->sockfd);
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede finalizar la conexion.");
        throw;
    }
}

int Conexion::Escribir(const string& pMensaje) throw (Excepcion&){
    
    try
    {
        /* Toma el mensaje, y lo escribe a traves de la conexion.
         */
        string msg_env = pMensaje;
        
        // Finalizo el mensaje para transmision.
        msg_env += MARCA_FIN_MENSAJE;

        // Envio el mensaje.
        
        int intentos = this->timeout;
        int cntBytes = 0;
        uint32_t byteIndex = 0;
        
        while (intentos >= 0)
        {
            cntBytes = send (this->sockfd, &(msg_env.c_str()[byteIndex]), msg_env.size()- byteIndex, 0);
            
            if (cntBytes > 0)
            {
                byteIndex += cntBytes;
                
                if (msg_env.length() == byteIndex)
                    break;
                else
                    intentos--;
            }
            else
            {
                // Hubo un error en la comunicacion.
                
                if (0 == cntBytes)
                    throw Excepcion ("Perdida de conexion.");
                else
                    throw Excepcion ("Fallo de comunicacion.");
            }
        }
        
        if (intentos < 0)
            throw Excepcion ("Tiempo de conexion acabado.");
        
        return msg_env.length();
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede escribir el mensaje.");
        throw;
    }
}

int Conexion::Leer (string& pMensaje) throw (Excepcion&){

    /* Espera por un mensaje entrante.  Cuando lo recibe, lo almacena en el 
     * parametro pMensaje.
     * 
     */
    try
    {
        uint16_t longitudMarca = strlen(MARCA_FIN_MENSAJE);
        char* buffer = new char [longitudMarca + 1];
        
        int intentos = this->timeout;
        
        // Inicializo el buffer para lectura.

        intentos = this->timeout;
        bzero(buffer,longitudMarca + 1);
        int indiceByte = 0;

        while (indiceByte < longitudMarca)
        {
            int bytes_leidos = recv (this->sockfd, &(buffer[indiceByte]), sizeof (char), 0);
            int errnov = errno;

            if (bytes_leidos <= 0) 
            {
                intentos--;
                if (intentos < 0) // Timeout.
                    throw errnov;
            }
            else
            {
                indiceByte++;
            }
        }

        intentos = this->timeout;
        pMensaje = "";
        
        while (true)
        {
            if (0 == strcmp (buffer, MARCA_FIN_MENSAJE))
            {
                // Fin del comunicado.
                break;
            }
            else
            {
                // Se lee del buffer el proximo byte.
                
                pMensaje += (char) buffer[0];
                
                // Se actualiza el buffer.
                int i = 0;
                while (i < longitudMarca)
                {
                    buffer[i] = buffer[i+1];
                    i++;
                }

                // Se lee el proximo byte desde el socket.
                
                int bytes_leidos = recv (this->sockfd, &(buffer[longitudMarca - 1]), sizeof (char), 0);
                int errnov = errno;

                if (bytes_leidos <= 0) 
                {
                    intentos--;
                    if (intentos < 0) // Timeout.
                        throw errnov;
                }
            }
        }  
        
        delete [] buffer;
        return static_cast <int> (pMensaje.length());
    }
    catch (int ex)
    {
        string msg = "ERROR DE CONEXION.";
        decodificarErrorRecepcion(msg, ex);
        throw Excepcion (msg);
    }
}

void Conexion::decodificarErrorRecepcion (string &pMensaje, int& pCodigoError){
    
    switch (pCodigoError)
    {
        case EACCES:
        pMensaje += "(For UNIX domain sockets, which are identified by pathname) Write permission is denied on the destination socket file, or search permission is denied for one of the directories the path prefix. (See path_resolution(7).).  (For UDP sockets) An attempt was made to send to a network/broadcast address as though it was a unicast address.";
        break;

        case EAGAIN:
        pMensaje += "The socket is marked nonblocking and the requested operation would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities. ";
        break;

        case EBADF:
        pMensaje += "An invalid descriptor was specified.";
        break;

        case ECONNRESET:
        pMensaje += "Connection reset by peer.";
        break;

        case EDESTADDRREQ:
        pMensaje += "The socket is not connection-mode, and no peer address is set.";
        break;

        case EFAULT:
        pMensaje += "An invalid user space address was specified for an argument.";
        break;

        case EINTR:
        pMensaje += "A signal occurred before any data was transmitted; see signal(7).";
        break;

        case EINVAL:
        pMensaje += "Invalid argument passed.";
        break;

        case EISCONN:
        pMensaje += "The connection-mode socket was connected already but a recipient was specified. (Now either this error is returned, or the recipient specification is ignored.) ";
        break;

        case EMSGSIZE:
        pMensaje += "The socket type requires that message be sent atomically, and the size of the message to be sent made this impossible. ";
        break;

        case ENOBUFS:
        pMensaje += "The output queue for a network interface was full. This generally indicates that the interface has stopped sending, but may be caused by transient congestion. (Normally, this does not occur in Linux. Packets are just silently dropped when a device queue overflows.) ";
        break;

        case ENOMEM:
        pMensaje += "No memory available.";
        break;

        case ENOTCONN:
        pMensaje += "The socket is not connected, and no target has been given. ";
        break;

        case ENOTSOCK:
        pMensaje += "The argument sockfd is not a socket. ";
        break;

        case EOPNOTSUPP:
        pMensaje += "Some bit in the flags argument is inappropriate for the socket type. ";
        break;

        case EPIPE:
        pMensaje += "The local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set. ";
        break;
        
        default: 
            pMensaje += "Error desconocido.";
    }

}
